﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using ArgusPCLib.Collections.Generic;

namespace ArgusPCLib.Bird
{
	//public delegate void UnhandledExceptionHandler(object sender, object exceptionObject);

	public static class Logger
	{
		public const string CompilationSymbol = "LOG";

		public static LogVerbosities Verbosity { get; set; }
		public static ILogWriter LogWriter { get; set; }
		public static bool IsLogWriterAvailable { get { return Logger.LogWriter != null && !Logger.LogWriter.IsDisposed; } }
		public static ItemCollection<string> FilePathCensorValues { get; private set; }
		public static ItemCollection<string> FilePathFilters { get; private set; }
		public static FilePathFilterTypes FilePathFilterType { get; set; }
		public static string CensoredPathReplacement { get; set; }

		static Logger()
		{
			Logger.CensoredPathReplacement = "{CensoredPath}";
#if DEBUG
			Logger.Verbosity = LogVerbosities.Messages;
#else
			Logger.Verbosity = LogVerbosities.Warnings;
#endif
			Logger.FilePathCensorValues = new ItemCollection<string>();
			Logger.FilePathCensorValues.ItemChanged += Logger.ItemCollection_ItemChanged;
			Logger.FilePathFilters = new ItemCollection<string>();
			Logger.FilePathFilters.ItemChanged += Logger.ItemCollection_ItemChanged;
			Logger.FilePathFilterType = FilePathFilterTypes.Include;

			string path = Properties.Resources.SolutionDirectoryFile.Trim().TrimEnd('\\', '/');
			path = System.IO.Path.GetDirectoryName(path);
			Logger.FilePathCensorValues.Add(path);
		}

		static void ItemCollection_ItemChanged(object sender, ItemChangedEventArgs<string> e)
		{
			if (e.ChangeType != ChangeTypes.Added)
				return;
			ItemCollection<string> Sender = (ItemCollection<string>)sender;
			Sender[e.ItemIndex] = e.NewValue.ToLower();
		}

		public static string RemoveCensoredPathParts(string filePath)
		{
			string filePathLower = filePath.ToLower();
			foreach (string s in Logger.FilePathCensorValues)
			{
				int i = filePathLower.IndexOf(s);
				if (i > -1)
					filePath = filePath.Remove(i, s.Length).Insert(i, Logger.CensoredPathReplacement);
			}
			return filePath;
		}

		public static bool ConformsWithFilters(string filePath)
		{
			if (Logger.FilePathFilters.Count < 1)
				return true;
			filePath = filePath.ToLower();
			bool fits = filePath.FitsAny(Logger.FilePathFilters, false);
			if (Logger.FilePathFilterType == FilePathFilterTypes.Include)
				return fits;
			else
				return !fits;
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMessage(LogVerbosities verbosity, string message,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				Logger.LogWriter.LogMessage(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, message);
			}
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMethodEnter(LogVerbosities verbosity, Delegate methodHandle, object[] arguments,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				Logger.LogWriter.LogMethodEnter(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, methodHandle, arguments);
			}
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMethodLeave(LogVerbosities verbosity, Delegate methodHandle, object[] returnValues,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				Logger.LogWriter.LogMethodLeave(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, methodHandle, returnValues);
			}
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMethodEnter(LogVerbosities verbosity, MethodInfo methodInfo = null, object[] arguments = null,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				Logger.LogWriter.LogMethodEnter(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, methodInfo, arguments);
			}
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMethodLeave(LogVerbosities verbosity, MethodInfo methodInfo = null, object[] returnValues = null,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				Logger.LogWriter.LogMethodLeave(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, methodInfo, returnValues);
			}
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMethodEnter(ref MethodInfo methodInfo, LogVerbosities verbosity, Type callingType, object[] arguments = null,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			Logger.LogMethodEnterCore(ref methodInfo, verbosity, callingType, arguments, callerName, callerFilePath, callerLineNumber);
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogMethodEnter(LogVerbosities verbosity, Type callingType, object[] arguments = null,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			MethodInfo methodInfo = null;
			Logger.LogMethodEnterCore(ref methodInfo, verbosity, callingType, arguments, callerName, callerFilePath, callerLineNumber);
		}

		static void LogMethodEnterCore(ref MethodInfo methodInfo, LogVerbosities verbosity, Type callingType, object[] arguments,
			string callerName, string callerFilePath, int callerLineNumber)
		{
			methodInfo = null;
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				if (callingType != null)
				{
					Type[] parameters;
					if (arguments == null)
						parameters = new Type[0];
					else
					{
						parameters = new Type[arguments.Length];
						for (int i = 0; i < arguments.Length; i++)
							parameters[i] = arguments[i].GetType();
					}
					methodInfo = callingType.GetRuntimeMethod(callerName, parameters, true, true);

					if (methodInfo == null)
					{
						if (parameters.Length < 1)
							methodInfo = callingType.GetRuntimeMethod("get_" + callerName, parameters, true, true);
						else if (parameters.Length == 1)
							methodInfo = callingType.GetRuntimeMethod("set_" + callerName, parameters, true, true);
					}
				}
				Logger.LogWriter.LogMethodEnter(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, methodInfo, arguments);
			}
		}

		[Conditional(Logger.CompilationSymbol)]
		public static void LogExceptionThrown(LogVerbosities verbosity, Exception exception,
			[CallerMemberName] string callerName = "",
			[CallerFilePath] string callerFilePath = "",
			[CallerLineNumber] int callerLineNumber = 0)
		{
			if (Logger.IsLogWriterAvailable && Logger.Verbosity < LogVerbosities.None
				&& verbosity >= Logger.Verbosity && Logger.ConformsWithFilters(callerFilePath))
			{
				Logger.LogWriter.LogExceptionThrown(callerName, Logger.RemoveCensoredPathParts(callerFilePath), callerLineNumber, new ExceptionWrapper(exception));
			}
		}
	}
}
